package com.cppba.alipay.util;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

/**
 * @author winfed
 * @create 2017-10-18 17:19
 */
@Slf4j
public abstract class HttpClientUtils implements HttpStatus {

    private static final String USER_AGENT = "Mozilla/5.0";

    private static final int DEFAULT_CONN_TIMEOUT_MILLISECONDS = 30 * 1000;

    private static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";
    private static final String ENCODING_GZIP = "gzip";

    private static final RequestConfig DEFAULT_REQUEST_CONFIG = RequestConfig.custom().setConnectionRequestTimeout(DEFAULT_CONN_TIMEOUT_MILLISECONDS)                //
            .setSocketTimeout(DEFAULT_CONN_TIMEOUT_MILLISECONDS)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                //
            .setConnectTimeout(DEFAULT_CONN_TIMEOUT_MILLISECONDS)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        //
            .setConnectionRequestTimeout(DEFAULT_CONN_TIMEOUT_MILLISECONDS)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        //
            .build();

    enum HttpMethod {
        GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE
    }

    private HttpClientUtils() {
    }

    public static HttpClient createDefault() {
        return org.apache.http.impl.client.HttpClients.createDefault();
    }

    public static HttpClient newHttpClient(RequestConfig rc) {
        Header header = new BasicHeader(HttpHeaders.USER_AGENT, USER_AGENT);
        List<Header> headers = Lists.newArrayList(header);
        HttpClientBuilder builder = org.apache.http.impl.client.HttpClients.custom().setDefaultHeaders(headers);
        builder.setDefaultRequestConfig(rc);
        HttpClient client = builder.build();
        return client;
    }

    public static HttpClient newHttpClient() {
        return newHttpClient(DEFAULT_REQUEST_CONFIG);
    }

    protected static HttpUriRequest newHttpUriRequest(HttpMethod httpMethod, URI uri) {
        switch (httpMethod) {
            case GET:
                return new HttpGet(uri);
            case DELETE:
                return new HttpDelete(uri);
            case HEAD:
                return new HttpHead(uri);
            case OPTIONS:
                return new HttpOptions(uri);
            case POST:
                return new HttpPost(uri);
            case PUT:
                return new HttpPut(uri);
            case TRACE:
                return new HttpTrace(uri);
            default:
                throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
        }
    }

    protected static HttpUriRequest newHttpUriRequest(HttpMethod httpMethod, String url) {
        return newHttpUriRequest(httpMethod, URI.create(url));
    }

    protected static HttpUriRequest newGetHttpUriRequest(String url) {
        return newHttpUriRequest(HttpMethod.GET, url);
    }

    protected static HttpUriRequest newPostHttpUriRequest(String url) {
        return newHttpUriRequest(HttpMethod.POST, url);
    }

    protected static HttpUriRequest newPostHttpUriRequest(String url, HttpEntity entity) {
        HttpPost request = (HttpPost) newHttpUriRequest(HttpMethod.POST, url);
        request.setEntity(entity);
        return request;
    }

    protected static UrlEncodedFormEntity newUrlEncodedFormEntity(List<NameValuePair> params) throws UnsupportedEncodingException {
        return new UrlEncodedFormEntity(params, Charsets.UTF_8);
    }

    protected static UrlEncodedFormEntity newUrlEncodedFormEntity(Map<String, ?> map) throws UnsupportedEncodingException {
        List<BasicNameValuePair> parameters = Lists.newArrayList();
        for (Map.Entry<String, ?> entry : map.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof Collection) {
                Collection<?> values = (Collection<?>) value;
                for (Object v : values) {
                    // This will add a parameter for each value in the Collection/List
                    parameters.add(new BasicNameValuePair(entry.getKey(), v == null ? null : String.valueOf(v)));
                }
            } else {
                parameters.add(new BasicNameValuePair(entry.getKey(), value == null ? null : String.valueOf(value)));
            }
        }
        return new UrlEncodedFormEntity(parameters, Charsets.UTF_8);
    }

    protected static String execute(HttpUriRequest method) {
        String result = null;
        CloseableHttpClient client = (CloseableHttpClient) newHttpClient();
        try {
            CloseableHttpResponse response = null;
            try {
                response = client.execute(method);
                StatusLine status = response.getStatusLine();
                if (status.getStatusCode() == 200) {
                    HttpEntity entity = response.getEntity();
                    result = EntityUtils.toString(entity, Charsets.UTF_8);
                    EntityUtils.consumeQuietly(entity);// ensure it is fully consumed
                } else {
                    throw new NoHttpResponseException("Did not receive successful HTTP response: status code = " + status.getStatusCode() + ", status message = [" + status.getReasonPhrase() + "]");
                }
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            } finally {
                try {
                    if (response != null)
                        response.close();
                } catch (IOException ex) {
                    // ignore
                    log.error("", ex);
                }
            }
        } finally {
            try {
                client.close();
            } catch (IOException ex) {
                // ignore
                log.error("", ex);
            }
        }
        return result;
    }

    public static String get(String url) {
        return execute(newGetHttpUriRequest(url));
    }

    public static String get(String url, Map<String, Object> params) {
        return get(new StringBuffer().append(url).append("?").append(UriVariableUtils.getMapToParameters(params)).toString());
    }

    public static String post(String url) {
        return execute(newPostHttpUriRequest(url));
    }

    protected static String post(String url, HttpEntity entity) {
        return execute(newPostHttpUriRequest(url, entity));
    }

    public static String post(String url, String param) {
        return execute(newPostHttpUriRequest(url, new StringEntity(param, Charsets.UTF_8)));
    }

    protected static String post(String url, UrlEncodedFormEntity params) throws IOException {
        return execute(newPostHttpUriRequest(url, params));
    }

    public static String post(String url, Map<String, Object> params) throws IOException {
        return post(url, newUrlEncodedFormEntity(params));
    }

    public static String post(String url, List<NameValuePair> params) throws IOException {
        return post(url, newUrlEncodedFormEntity(params));
    }

    /**
     * http://www.cnblogs.com/always-online/p/3899224.html
     */
    public static String upload(String url, File file) {
        FileBody bin = new FileBody(file);// 文件
        HttpEntity entity = MultipartEntityBuilder.create().addPart("media", bin).build();// 请求体. media为文件对于的key值
        return post(url, entity);
    }

    /**
     * Determine whether the given response indicates a GZIP response.
     * <p/>
     * The default implementation checks whether the HTTP "Content-Encoding" header contains "gzip" (in any casing).
     *
     * @param httpResponse the resulting HttpResponse to check
     * @return whether the given response indicates a GZIP response
     */
    protected static boolean isGzipResponse(HttpResponse httpResponse) {
        Header encodingHeader = httpResponse.getFirstHeader(HTTP_HEADER_CONTENT_ENCODING);
        return (encodingHeader != null && encodingHeader.getValue() != null && encodingHeader.getValue().toLowerCase().contains(ENCODING_GZIP));
    }

    /**
     * Extract the response body
     * <p/>
     * The default implementation simply fetches the response body stream. If the response is recognized as GZIP response, the InputStream will get wrapped in a GZIPInputStream.
     *
     * @param httpResponse the resulting HttpResponse to read the response body
     *                     from
     * @return an InputStream for the response body
     * @throws IOException if thrown by I/O methods
     * @see #isGzipResponse
     * @see GZIPInputStream
     */
    protected static InputStream getResponseBody(HttpResponse httpResponse) throws IOException {
        if (isGzipResponse(httpResponse)) {
            return new GZIPInputStream(httpResponse.getEntity().getContent());
        } else {
            return httpResponse.getEntity().getContent();
        }
    }

    /**
     * Validate the given response, throwing an exception if it does not
     * correspond to a successful HTTP response.
     * <p/>
     * Default implementation rejects any HTTP status code beyond 2xx, to avoid parsing the response body and trying to deserialize from a corrupted stream.
     *
     * @param response the resulting HttpResponse to validate
     * @throws NoHttpResponseException
     * @throws IOException             if validation failed
     */
    protected static void validateResponse(HttpResponse response) throws IOException {
        StatusLine status = response.getStatusLine();
        if (status.getStatusCode() >= 300) {
            throw new NoHttpResponseException("Did not receive successful HTTP response: status code = " + status.getStatusCode() + ", status message = [" + status.getReasonPhrase() + "]");
        }
    }

}
